package study.service.user;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import study.domain.user.Authority;
import study.domain.user.Role;
import study.domain.user.RoleHierarchy;
import study.domain.user.User;
import study.domain.user.UserSearch;
import study.repository.user.AuthorityRepository;
import study.repository.user.RoleHierarchyRepository;
import study.repository.user.RoleRepository;
import study.repository.user.UserRepository;
import study.repository.user.UserSpecifications;

import com.barney.support.web.WebList;

/**
 * 사용자 서비스
 *
 * @author Barney Kim
 */
@Service
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public class UserService {

	@Resource(name = "userRepository")
	private UserRepository userRepository;

	@Resource(name = "authorityRepository")
	private AuthorityRepository authorityRepository;

	@Resource(name = "roleRepository")
	private RoleRepository roleRepository;

	@Resource(name = "roleHierarchyRepository")
	private RoleHierarchyRepository roleHierarchyRepository;

	/**
	 * 사용자 목록을 반환한다.
	 *
	 * @param search 검색
	 */
	public WebList<User> getUsers(UserSearch search) {
		WebList<User> result = new WebList<User>();
		result.setTotal(userRepository.getCount(search));
		if (result.isNotEmpty()) {
			result.setList(userRepository.getList(search));
		}
		return result;
	}

	/**
	 * 사용자 ID로 검색하여 사용자를 반환한다.
	 *
	 * @param userId 사용자 ID
	 */
	public User getUserByUserId(Long userId) {
		return userRepository.findByUserId(userId);
	}

	/**
	 * 사용자 명을 검색하여 사용자를 반환한다.
	 *
	 * @param username 사용자명
	 */
	public User getUserByUsername(String username) {
		return userRepository.findByUsername(username);
	}

	/**
	 * 이미 사용자명이 있는지 여부를 반환한다.
	 *
	 * @param username 사용자 명
	 * @return 있으면 <code>true</code>
	 */
	public boolean hasUserByUsername(String username) {
		return userRepository.count(UserSpecifications.findByUsername(username)) == 1;
	}

	/**
	 * 사용자를 추가한다.
	 *
	 * @param user 사용자
	 * @return 추가된 사용자
	 */
	@Transactional(readOnly = false)
	public User addUser(User user) {
		return userRepository.save(user);
	}

	/**
	 * 사용자를 추가하고 ROLE_USER 역활을 준다.
	 *
	 * @param user 사용자
	 * @return 추가된 사용자
	 */
	@Transactional(readOnly = false)
	public User addNormalUser(User user) {
		addUser(user);
		addAuthority(user, roleRepository.findByRole("ROLE_USER"));
		return user;
	}

	/**
	 * 사용자를 변경한다.
	 *
	 * @param user 사용자
	 * @return 변경된 사용자
	 */
	@Transactional(readOnly = false)
	public User setUser(User user) {
		return userRepository.save(user);
	}

	/**
	 * 사용자를 제거한다.
	 * 1) 사용자가 소유한 권한을 삭제
	 * 2) 사용자 삭제
	 *
	 * @param userId 사용자 ID
	 *
	 * @return 예외가 발생하면 <tt>false</tt>
	 */
	@Transactional(readOnly = false)
	public boolean removeUser(Long userId) {
		try {
			removeAuthority(userId);
			userRepository.delete(userId);
			return true;
		} catch (InvalidDataAccessApiUsageException idae) {
			// ignore
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return false;
	}

	/**
	 * 사용자 권한 목록을 반환한다.
	 *
	 * @param userId 사용자 ID
	 */
	public List<Authority> getAuthorities(Long userId) {
		return authorityRepository.findAllById_User_UserId(userId);
	}

	/**
	 * 사용자에게 권한을 추가한다.
	 *
	 * @param user 사용자
	 * @param role 권한
	 */
	@Transactional(readOnly = false)
	public Authority addAuthority(User user, Role role) {
		Authority authority = new Authority(user, role);
		return authorityRepository.save(authority);
	}

	/**
	 * 사용자가 소유한 모든 권한을 삭제한다.
	 *
	 * @param userId 사용자 ID
	 */
	@Transactional(readOnly = false)
	public void removeAuthority(Long userId) {
		List<Authority> authorities = getAuthorities(userId);
		for (Authority a : authorities) {
			authorityRepository.delete(a);
		}
	}

	/**
	 * Spring Security에서 사용할 역활 계층을 문자열로 반환한다.
	 *
	 * @see <a href="http://static.springsource.org/spring-security/site/docs/3.1.x/reference/authz-arch.html#authz-hierarchical-roles">Authorization Architecture - Hierarchical Roles</a>
	 */
	public String getRoleHierarchyString() {
		List<RoleHierarchy> list = roleHierarchyRepository.getRoleHierarchyList();
		StringBuilder sb = new StringBuilder();
		for (RoleHierarchy r : list) {
			sb.append(r.getId().getChildRole().getRole()).append(" > ")
					.append(r.getId().getParentRole().getRole()).append("\n");
		}
		return sb.toString();
	}
}
